﻿using OpenTK.Compute.OpenCL;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using System.Net;

namespace TrinityEngine
{
    public class Shader : IDisposable
    {
        private bool _disposed = false;

        private int _rendererID;

        private Dictionary<string, int> _uniformLocationCache = new Dictionary<string, int>();

        public Shader(string shaderPath)
        {
            string shaderSource = File.ReadAllText(shaderPath);
            int split = shaderSource.LastIndexOf('#');
            string vertexSource = shaderSource[0..split];
            string fragmentSource = shaderSource[split..];

            int vertexShader = CompileShader(vertexSource, ShaderType.VertexShader);
            int fragmentShader = CompileShader(fragmentSource, ShaderType.FragmentShader);

            _rendererID = CreateShader(vertexShader, fragmentShader);

            GL.UseProgram(_rendererID);
        }

        ~Shader()
        {
            Dispose(false);
        }

        private int CompileShader(string source, ShaderType type)
        {
            int address = GL.CreateShader(type);
            GL.ShaderSource(address, source);
            GL.CompileShader(address);
#if DEBUG
            GL.GetShader(address, ShaderParameter.CompileStatus, out var code);
            if (code == 0)
            {
                Debug.Log(GL.GetShaderInfoLog(address));
            }
#endif
            return address;
        }

        private int CreateShader(int vertexShader, int fragmentShader)
        {
            int program = GL.CreateProgram();

            GL.AttachShader(program, vertexShader);
            GL.AttachShader(program, fragmentShader);
            GL.LinkProgram(program);

#if DEBUG
            GL.GetProgram(program, GetProgramParameterName.LinkStatus, out var code);
            if (code == 0)
            {
                Debug.Log(GL.GetProgramInfoLog(program));
            }
#endif

            GL.DeleteShader(vertexShader);
            GL.DeleteShader(fragmentShader);

            return program;
        }

        public void Bind()
        {
            GL.UseProgram(_rendererID);
        }

        public void Unbind()
        {
            GL.UseProgram(0);
        }

        public void SetUniform1i(string name, int value)
        {
            GL.Uniform1(GetUniformLocation(name), value);
        }

        public void SetUniform1i(int location, int value)
        {
            GL.Uniform1(location, value);
        }

        public void SetUniform1f(string name, float value)
        {
            GL.Uniform1(GetUniformLocation(name), value);
        }

        public void SetUniformVec2(string name, OpenTK.Mathematics.Vector2 value)
        {
            GL.Uniform2(GetUniformLocation(name), value);
        }

        public void SetUniformVec3(string name, Vector3 value)
        {
            GL.Uniform3(GetUniformLocation(name), value);
        }

        public void SetUniformVec4(string name, Vector4 value)
        {
            GL.Uniform4(GetUniformLocation(name), value);
        }

        public void SetUniformMat4(string name, Matrix4 mat)
        {
            GL.UniformMatrix4(GetUniformLocation(name), true, ref mat);
        }

        private int GetUniformLocation(string name)
        {
            if (_uniformLocationCache.ContainsKey(name))
            {
                return _uniformLocationCache[name];
            }
            else
            {
                int location = GL.GetUniformLocation(_rendererID, name);

                if (location == -1)
                {
                    throw new Exception($"{name}: Uniform not found!");
                }

                _uniformLocationCache.Add(name, location);
                return location;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            if (disposing)
            {
                _uniformLocationCache = null;
            }

            GL.DeleteProgram(_rendererID);

            _disposed = true;
        }
    }
}
